﻿// ===================================
// <copyright>LogoUI Co.</copyright>
// <author>Vlad Spivak</author>
// <email>mailto:vlads@logoui.co.il</email>
// <created>20/00/10</created>
// <lastedit>Saturday, November 20, 2010</lastedit>

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the 'Software'), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
//
// <remarks>Part of this software based on various internet sources, mostly on the works
// of members of Wpf Disciples group http://wpfdisciples.wordpress.com/
// Also project may contain code from the frameworks: 
//        Nito 
//        OpenLightGroup
//        nRoute
// </remarks>
// ====================================================================================//


#if SILVERLIGHT
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace LogoFX.Infra
{
    public class PropertySetter
    {
        [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods",
            Justification = "Unambiguous in XAML.")]
        public string Type { get; set; }
        public string Property { get; set; }
        public Binding Binding { get; set; }
    }

    /// <summary>
    /// Represent workaround to fight inability to use bindings in property setters in Silverlight
    /// <example>
    ///        <Setter Property="Infra:SetterValueBindingHelper.PropertyBinding">
    ///            <Setter.Value>
    ///                <Infra:SetterValueBindingHelper>
    ///                <Infra:SetterValueBindingHelper.Setters>
    ///                        <Infra:PropertySetter Property="Header" Binding="{Binding DisplayName}"/>
    ///                        <Infra:PropertySetter Property="IsSelected" Binding="{Binding IsSelected,Mode=TwoWay}"/>
    ///                        <Infra:PropertySetter Property="IsEnabled" Binding="{Binding IsEnabled}"/>
    ///                    </Infra:SetterValueBindingHelper.Setters>
    ///                </Infra:SetterValueBindingHelper>
    ///            </Setter.Value>
    ///        </Setter>
    /// </example>
    /// </summary>
    public class SetterValueBindingHelper
    {
        /// <summary>
        /// Optional type parameter used to specify the type of an attached
        /// DependencyProperty as an assembly-qualified name, full name, or
        /// short name.
        /// </summary>

        private ObservableCollection<PropertySetter> m_setters = new ObservableCollection<PropertySetter>();
        ///// <summary>
        ///// Property name for the normal/attached DependencyProperty on which
        ///// to set the Binding.
        ///// </summary>
        //public string Property { get; set; }

        ///// <summary>
        ///// Binding to set on the specified property.
        ///// </summary>
        //public Binding Binding { get; set; }

        /// <summary>
        /// Gets the value of the PropertyBinding attached DependencyProperty.
        /// </summary>
        /// <param name="element">Element for which to get the property.</param>
        /// <returns>Value of PropertyBinding attached DependencyProperty.</returns>
        [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters",
            Justification = "SetBinding is only available on FrameworkElement.")]
        public static SetterValueBindingHelper GetPropertyBinding(FrameworkElement element)
        {
            if (null == element)
            {
                throw new ArgumentNullException("element");
            }
            return (SetterValueBindingHelper)element.GetValue(PropertyBindingProperty);
        }

        /// <summary>
        /// Sets the value of the PropertyBinding attached DependencyProperty.
        /// </summary>
        /// <param name="element">Element on which to set the property.</param>
        /// <param name="value">Value forPropertyBinding attached DependencyProperty.</param>
        [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters",
            Justification = "SetBinding is only available on FrameworkElement.")]
        public static void SetPropertyBinding(FrameworkElement element, SetterValueBindingHelper value)
        {
            if (null == element)
            {
                throw new ArgumentNullException("element");
            }
            element.SetValue(PropertyBindingProperty, value);
        }

        /// <summary>
        /// PropertyBinding attached DependencyProperty.
        /// </summary>
        public static readonly DependencyProperty PropertyBindingProperty =
            DependencyProperty.RegisterAttached(
                "PropertyBinding",
                typeof(SetterValueBindingHelper),
                typeof(SetterValueBindingHelper),
                new PropertyMetadata(null, OnPropertyBindingPropertyChanged));

        /// <summary>
        /// Change handler for the PropertyBinding attached DependencyProperty.
        /// </summary>
        /// <param name="d">Object on which the property was changed.</param>
        /// <param name="e">Property change arguments.</param>
        private static void OnPropertyBindingPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            // Get/validate parameters
            var element = (FrameworkElement)d;
            var item = (SetterValueBindingHelper)(e.NewValue);
            
            if (item == null) return;
            // Get the type on which to set the Binding

            // Get the DependencyProperty for which to set the Binding
            foreach (PropertySetter setter in item.Setters)
            {
                DependencyProperty property = null;
                Type type = null;
                if (null == setter.Type)
                {
                    // No type specified; setting for the specified element
                    type = element.GetType();
                }
                else
                {
                    // Try to get the type from the type system
                    try
                    {
                        type = System.Type.GetType(setter.Type);
                    }
                    catch
                    {
                    }
                    if (null == type)
                    {
                        // Search for the type in the list of assemblies
                        foreach (var assembly in AssembliesToSearch)
                        {
                            // Match on short or full name
                            PropertySetter setter1 = setter;
                            type = assembly.GetTypes()
                                .Where(t => (t.FullName == setter1.Type) || (t.Name == setter1.Type))
                                .FirstOrDefault();
                            if (null != type)
                            {
                                // Found; done searching
                                break;
                            }
                        }
                        if (null == type)
                        {
                            // Unable to find the requested type anywhere
                            throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
                                "Unable to access type \"{0}\". Try using an assembly qualified type name.",
                                setter.Type));
                        }
                    }
                }

                var field = type.GetField(setter.Property + "Property",
                    BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Static);
                if (null != field)
                {
                    property = field.GetValue(null) as DependencyProperty;
                }
                if (null == property)
                {
                    // Unable to find the requested property
                    throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
                        "Unable to access DependencyProperty \"{0}\" on type \"{1}\".",
                        setter.Property, type.Name));
                }

                // Set the specified Binding on the specified property
                element.SetBinding(property, setter.Binding);

            }
        }

        /// <summary>
        /// Returns a stream of assemblies to search for the provided type name.
        /// </summary>
        private static IEnumerable<Assembly> AssembliesToSearch
        {
            get
            {
                // Start with the System.Windows assembly (home of all core controls)
                yield return typeof(Control).Assembly;

                // Fall back by trying each of the assemblies in the Deployment's Parts list
                foreach (var part in Deployment.Current.Parts)
                {
                    var streamResourceInfo = Application.GetResourceStream(
                        new Uri(part.Source, UriKind.Relative));
                    using (var stream = streamResourceInfo.Stream)
                    {
                        yield return part.Load(stream);
                    }
                }
            }
        }

        public ObservableCollection<PropertySetter> Setters
        {
            get { return m_setters; }
        }
    }
}

#endif
